#include "json/reader.h"
#include "json/value.h"
#include <utility>
#include <cstdio>
#include <cassert>
#include <cstring>
#include <iostream>
#include <fstream>
#include <stdexcept>

#if _MSC_VER >= 1400 // VC++ 8.0
#pragma warning( disable : 4996 )   // disable warning about strdup being deprecated.
#endif

namespace Json {

	// Implementation of class Features
	// ////////////////////////////////

	Features::Features()
		: allowComments_( true )
		, strictRoot_( false )
	{
	}


	Features 
		Features::all()
	{
		return Features();
	}


	Features 
		Features::strictMode()
	{
		Features features;
		features.allowComments_ = false;
		features.strictRoot_ = true;
		return features;
	}

	// Implementation of class Reader
	// ////////////////////////////////


	static inline bool 
		in( Reader::Char c, Reader::Char c1, Reader::Char c2, Reader::Char c3, Reader::Char c4 )
	{
		return c == c1  ||  c == c2  ||  c == c3  ||  c == c4;
	}

	static inline bool 
		in( Reader::Char c, Reader::Char c1, Reader::Char c2, Reader::Char c3, Reader::Char c4, Reader::Char c5 )
	{
		return c == c1  ||  c == c2  ||  c == c3  ||  c == c4  ||  c == c5;
	}


	static bool 
		containsNewLine( Reader::Location begin, 
		Reader::Location end )
	{
		for ( ;begin < end; ++begin )
			if ( *begin == '\n'  ||  *begin == '\r' )
				return true;
		return false;
	}

	static std::string codePointToUTF8(unsigned int cp)
	{
		std::string result;

		// based on description from http://en.wikipedia.org/wiki/UTF-8

		if (cp <= 0x7f) 
		{
			result.resize(1);
			result[0] = static_cast<char>(cp);
		} 
		else if (cp <= 0x7FF) 
		{
			result.resize(2);
			result[1] = static_cast<char>(0x80 | (0x3f & cp));
			result[0] = static_cast<char>(0xC0 | (0x1f & (cp >> 6)));
		} 
		else if (cp <= 0xFFFF) 
		{
			result.resize(3);
			result[2] = static_cast<char>(0x80 | (0x3f & cp));
			result[1] = 0x80 | static_cast<char>((0x3f & (cp >> 6)));
			result[0] = 0xE0 | static_cast<char>((0xf & (cp >> 12)));
		}
		else if (cp <= 0x10FFFF) 
		{
			result.resize(4);
			result[3] = static_cast<char>(0x80 | (0x3f & cp));
			result[2] = static_cast<char>(0x80 | (0x3f & (cp >> 6)));
			result[1] = static_cast<char>(0x80 | (0x3f & (cp >> 12)));
			result[0] = static_cast<char>(0xF0 | (0x7 & (cp >> 18)));
		}

		return result;
	}


	// Class Reader
	// //////////////////////////////////////////////////////////////////

	Reader::Reader()
		: features_( Features::all() )
	{
	}


	Reader::Reader( const Features &features )
		: features_( features )
	{
	}


	bool Reader::parse( const std::string &document, Value &root, bool collectComments )
	{
		document_ = document;
		const char *begin = document_.c_str();
		const char *end = begin + document_.length();
		return parse( begin, end, root, collectComments );
	}


	bool Reader::parse( std::istream& sin, Value &root, bool collectComments )
	{
		//std::istream_iterator<char> begin(sin);
		//std::istream_iterator<char> end;
		// Those would allow streamed input from a file, if parse() were a
		// template function.

		// Since std::string is reference-counted, this at least does not
		// create an extra copy.
		std::string doc;
		std::getline(sin, doc, (char)EOF);
		return parse( doc, root, collectComments );
	}

	bool Reader::parse( const char *beginDoc, const char *endDoc, Value &root, bool collectComments )
	{
		if ( !features_.allowComments_ )
		{
			collectComments = false;
		}

		begin_ = beginDoc;
		end_ = endDoc;
		collectComments_ = collectComments;
		current_ = begin_;
		lastValueEnd_ = 0;
		lastValue_ = 0;
		commentsBefore_ = "";
		errors_.clear();
		while ( !nodes_.empty() )
			nodes_.pop();
		nodes_.push( &root );

		Token token;
		bool successful = readValue();
		skipCommentTokens( token );
		if ( collectComments_  &&  !commentsBefore_.empty() )
			root.setComment( commentsBefore_, commentAfter );
		if ( features_.strictRoot_ )
		{
			if ( !root.isArray()  &&  !root.isObject() )
			{
				// Set error location to start of doc, ideally should be first token found in doc
				token.type_ = tokenError;
				token.start_ = beginDoc;
				token.end_ = endDoc;
				addError( "A valid JSON document must be either an array or an object value.",
					token );
				return false;
			}
		}
		return successful;
	}


	bool Reader::readValue()
	{
		Token token;
		skipCommentTokens( token );
		bool successful = true;

		if ( collectComments_  &&  !commentsBefore_.empty() )
		{
			currentValue().setComment( commentsBefore_, commentBefore );
			commentsBefore_ = "";
		}


		switch ( token.type_ )
		{
		case tokenObjectBegin:
			successful = readObject( token );
			break;
		case tokenArrayBegin:
			successful = readArray( token );
			break;
		case tokenFileBegin:
			successful = loadFile( token );
			break;
		case tokenNumber:
			successful = decodeNumber( token );
			break;
		case tokenString:
			successful = decodeString( token );
			break;
		case tokenTrue:
			currentValue() = true;
			break;
		case tokenFalse:
			currentValue() = false;
			break;
		case tokenNull:
			currentValue() = Value();
			break;
		default:
			return addError( "Syntax error: value, object or array expected.", token );
		}

		if ( collectComments_ )
		{
			lastValueEnd_ = current_;
			lastValue_ = &currentValue();
		}

		return successful;
	}

	void Reader::skipCommentTokens( Token &token )
	{
		if ( features_.allowComments_ )
		{
			do
			{
				readToken( token );
			}
			while ( token.type_ == tokenComment );
		}
		else
		{
			readToken( token );
		}
	}


	bool Reader::expectToken( TokenType type, Token &token, const char *message )
	{
		readToken( token );
		if ( token.type_ != type )
			return addError( message, token );
		return true;
	}


	bool Reader::readToken( Token &token )
	{
		skipSpaces();
		token.start_ = current_;
		Char c = getNextChar();
		bool ok = true;

		switch ( c )
		{
		case '{':
			token.type_ = tokenObjectBegin;
			break;
		case '}':
			token.type_ = tokenObjectEnd;
			break;
		case '[':
			token.type_ = tokenArrayBegin;
			break;
		case ']':
			token.type_ = tokenArrayEnd;
			break;
		case '<':
			token.type_ = tokenFileBegin;
			ok = readFile();
			break;
		case '>':
			token.type_ = tokenFileEnd;
			break;
		case '"':
			token.type_ = tokenString;
			ok = readString( false );
			break;
		case '/':
			token.type_ = tokenComment;
			ok = readComment();
			break;
		case '0':
		case '1':
		case '2':
		case '3':
		case '4':
		case '5':
		case '6':
		case '7':
		case '8':
		case '9':
		case '-':
			token.type_ = tokenNumber;
			readNumber();
			break;
		case 't':
			token.type_ = tokenTrue;
			ok = match( "rue", 3 );
			break;
		case 'f':
			token.type_ = tokenFalse;
			ok = match( "alse", 4 );
			break;
		case 'n':
			token.type_ = tokenNull;
			ok = match( "ull", 3 );
			break;
		case ',':
		case ';':
			token.type_ = tokenArraySeparator;
			break;
		case ':':
		case '=':
			token.type_ = tokenMemberSeparator;
			break;
		case 0:
			token.type_ = tokenEndOfStream;
			break;
		default:
			ok = false;
			break;
		}
		if ( !ok )
		{
			if( ( c >= 'a' && c <= 'z' ) || ( c >= 'A' && c <= 'Z' ) || c == '_' )
			{
				token.type_ = readString( true )?tokenString:tokenError;
			}
			else
			{
				token.type_ = tokenError;
			}
		}

		token.end_ = current_;
		return true;
	}


	void Reader::skipSpaces()
	{
		while ( current_ != end_ )
		{
			Char c = *current_;
			if ( c == ' '  ||  c == '\t'  ||  c == '\r'  ||  c == '\n' )
				++current_;
			else
				break;
		}
	}


	bool Reader::match( Location pattern, int patternLength )
	{
		if ( end_ - current_ < patternLength )
			return false;
		int index = patternLength;
		while ( index-- )
			if ( current_[index] != pattern[index] )
				return false;
		current_ += patternLength;
		return true;
	}


	bool Reader::readComment()
	{
		Location commentBegin = current_ - 1;
		Char c = getNextChar();
		bool successful = false;
		if ( c == '*' )
			successful = readCStyleComment();
		else if ( c == '/' )
			successful = readCppStyleComment();
		if ( !successful )
			return false;

		if ( collectComments_ )
		{
			CommentPlacement placement = commentBefore;
			if ( lastValueEnd_  &&  !containsNewLine( lastValueEnd_, commentBegin ) )
			{
				if ( c != '*'  ||  !containsNewLine( commentBegin, current_ ) )
					placement = commentAfterOnSameLine;
			}

			addComment( commentBegin, current_, placement );
		}
		return true;
	}


	void Reader::addComment( Location begin, Location end, CommentPlacement placement )
	{
		assert( collectComments_ );
		if ( placement == commentAfterOnSameLine )
		{
			assert( lastValue_ != 0 );
			lastValue_->setComment( std::string( begin, end ), placement );
		}
		else
		{
			if ( !commentsBefore_.empty() )
				commentsBefore_ += "\n";
			commentsBefore_ += std::string( begin, end );
		}
	}


	bool Reader::readCStyleComment()
	{
		while ( current_ != end_ )
		{
			Char c = getNextChar();
			if ( c == '*'  &&  *current_ == '/' )
				break;
		}
		return getNextChar() == '/';
	}


	bool Reader::readCppStyleComment()
	{
		while ( current_ != end_ )
		{
			Char c = getNextChar();
			if (  c == '\r'  ||  c == '\n' )
				break;
		}
		return true;
	}


	void Reader::readNumber()
	{
		while ( current_ != end_ )
		{
			if ( !(*current_ >= '0'  &&  *current_ <= '9')  &&
				!in( *current_, '.', 'e', 'E', '+', '-' ) )
				break;
			++current_;
		}
	}

	bool Reader::readString(bool simple_string)
	{
		Char c = 0;
		while ( current_ != end_ )
		{
			c = getNextChar();
			if ( c == '\\' )
				getNextChar();
			else if ( c == '"' )
				return true;
			else if ( simple_string )
			{
				if( c == ',' || c == ':' || c == '=' || c == ' ' || c == '\n' )
				{
					--current_;
					return true;
				}
				else if( !isdigit(c) && !isgraph(c) )
				{
					--current_;
					return true;
				}
			}
		}
		return false;
	}

	bool Reader::readObject( Token &tokenStart )
	{
		Token tokenName;
		std::string name;
		currentValue() = Value( objectValue );
		while ( readToken( tokenName ) )
		{
			bool initialTokenOk = true;
			while ( tokenName.type_ == tokenComment  &&  initialTokenOk )
				initialTokenOk = readToken( tokenName );
			if  ( !initialTokenOk )
				break;
			if ( tokenName.type_ == tokenObjectEnd )  // empty object
				return true;
			if ( tokenName.type_ != tokenString )
				break;

			name = "";
			if ( !decodeString( tokenName, name ) )
				return recoverFromError( tokenObjectEnd );

			Token colon;
			if ( !readToken( colon ) ||  colon.type_ != tokenMemberSeparator )
			{
				return addErrorAndRecover( "Missing ':' after object member name", 
					colon, 
					tokenObjectEnd );
			}
			Value &value = currentValue()[ name ];
			nodes_.push( &value );
			bool ok = readValue();
			nodes_.pop();

			if ( !ok ) // error already set
				return recoverFromError( tokenObjectEnd );

			Token comma;
			if ( !readToken( comma )
				||  ( comma.type_ != tokenObjectEnd  &&  
				comma.type_ != tokenArraySeparator &&
				comma.type_ != tokenComment ) )
			{
				return addErrorAndRecover( "Missing ',' or '}' in object declaration", 
					comma, 
					tokenObjectEnd );
			}
			bool finalizeTokenOk = true;
			while ( comma.type_ == tokenComment &&
				finalizeTokenOk )
				finalizeTokenOk = readToken( comma );
			if ( comma.type_ == tokenObjectEnd )
				return true;
		}
		return addErrorAndRecover( "Missing '}' or object member name", 
			tokenName, 
			tokenObjectEnd );
	}


	bool Reader::readArray( Token &tokenStart )
	{
		currentValue() = Value( arrayValue );
		skipSpaces();
		if ( *current_ == ']' ) // empty array
		{
			Token endArray;
			readToken( endArray );
			return true;
		}
		int index = 0;
		while ( true )
		{
			Token token;
			readToken( token );
			if( token.type_ == tokenArrayEnd )
			{
				break;
			}

			current_ = token.start_;

			Value &value = currentValue()[ index++ ];
			nodes_.push( &value );
			bool ok = readValue();
			nodes_.pop();

			if ( !ok ) // error already set
				return recoverFromError( tokenArrayEnd );

			// Accept Comment after last item in the array.
			ok = readToken( token );
			while ( token.type_ == tokenComment  &&  ok )
			{
				ok = readToken( token );
			}

			bool badTokenType = ( token.type_ == tokenArraySeparator  ||  token.type_ == tokenArrayEnd );
			if ( !ok  ||  !badTokenType )
			{
				return addErrorAndRecover( "Missing ',' or ']' in array declaration", 
					token, 
					tokenArrayEnd );
			}
			if ( token.type_ == tokenArrayEnd )
				break;
		}
		return true;
	}

	bool Reader::readFile()
	{
		Char c = 0;
		while ( current_ != end_ )
		{
			c = getNextChar();
			if ( c == '>' )
				return true;
		}

		return false;
	}

	bool Reader::loadFile( Token &token )
	{
		std::string name("");
		if ( !decodeString( token, name ) )
			return recoverFromError( tokenFileEnd );

		bool ok = false;
		currentValue() = Value( objectValue );
		std::ifstream ifs( name.c_str() );
		if( ifs.is_open() )
		{
			Json::Reader reader;
			if( (ok = reader.parse( ifs, currentValue(), collectComments_ )) == false )
			{
				printf( "parse file %s error\n %s\n", name.c_str(), getFormatedErrorMessages().c_str() );
				currentValue().clear();
			}
		}

		return ok;
	}

	bool Reader::decodeNumber( Token &token )
	{
		bool isDouble = false;
		for ( Location inspect = token.start_; inspect != token.end_; ++inspect )
		{
			isDouble = isDouble  
				||  in( *inspect, '.', 'e', 'E', '+' )  
				||  ( *inspect == '-'  &&  inspect != token.start_ );
		}
		if ( isDouble )
			return decodeDouble( token );
		Location current = token.start_;
		bool isNegative = *current == '-';
		if ( isNegative )
			++current;
		Value::UInt threshold = (isNegative ? Value::UInt(-Value::minInt) 
			: Value::maxUInt) / 10;
		Value::UInt value = 0;
		while ( current < token.end_ )
		{
			Char c = *current++;
			if ( c < '0'  ||  c > '9' )
				return addError( "'" + std::string( token.start_, token.end_ ) + "' is not a number.", token );
			if ( value >= threshold )
				return decodeDouble( token );
			value = value * 10 + Value::UInt(c - '0');
		}
		if ( isNegative )
			currentValue() = -Value::Int( value );
		else if ( value <= Value::UInt(Value::maxInt) )
			currentValue() = Value::Int( value );
		else
			currentValue() = value;
		return true;
	}


	bool Reader::decodeDouble( Token &token )
	{
		double value = 0;
		const int bufferSize = 32;
		int count;
		int length = int(token.end_ - token.start_);
		if ( length <= bufferSize )
		{
			Char buffer[bufferSize];
			memcpy( buffer, token.start_, length );
			buffer[length] = 0;
			count = sscanf( buffer, "%lf", &value );
		}
		else
		{
			std::string buffer( token.start_, token.end_ );
			count = sscanf( buffer.c_str(), "%lf", &value );
		}

		if ( count != 1 )
			return addError( "'" + std::string( token.start_, token.end_ ) + "' is not a number.", token );
		currentValue() = value;
		return true;
	}


	bool Reader::decodeString( Token &token )
	{
		std::string decoded;
		if ( !decodeString( token, decoded ) )
			return false;
		currentValue() = decoded;
		return true;
	}


	bool Reader::decodeString( Token &token, std::string &decoded )
	{
		Location current = token.start_; // skip '"'
		Location end = token.end_;      // do not include '"'

		if( *token.start_ == '"' || *token.start_ == '<' )
		{
			decoded.reserve( token.end_ - token.start_ - 2 );
			current++; // skip '"'
			end--;      // do not include '"'
		}
		else
		{
			decoded.reserve( token.end_ - token.start_ );
		}

		while ( current != end )
		{
			Char c = *current++;
			if ( c == '"' )
				break;
			else if ( c == '\\' )
			{
				if ( current == end )
					return addError( "Empty escape sequence in string", token, current );
				Char escape = *current++;
				switch ( escape )
				{
				case '"': decoded += '"'; break;
				case '/': decoded += '/'; break;
				case '\\': decoded += '\\'; break;
				case 'b': decoded += '\b'; break;
				case 'f': decoded += '\f'; break;
				case 'n': decoded += '\n'; break;
				case 'r': decoded += '\r'; break;
				case 't': decoded += '\t'; break;
				case 'u':
					{
						unsigned int unicode;
						if ( !decodeUnicodeCodePoint( token, current, end, unicode ) )
							return false;
						decoded += codePointToUTF8(unicode);
					}
					break;
				default:
					return addError( "Bad escape sequence in string", token, current );
				}
			}
			else
			{
				decoded += c;
			}
		}
		return true;
	}

	bool Reader::decodeUnicodeCodePoint( Token &token, Location &current, Location end, unsigned int &unicode )
	{

		if ( !decodeUnicodeEscapeSequence( token, current, end, unicode ) )
			return false;
		if (unicode >= 0xD800 && unicode <= 0xDBFF)
		{
			// surrogate pairs
			if (end - current < 6)
				return addError( "additional six characters expected to parse unicode surrogate pair.", token, current );
			unsigned int surrogatePair;
			if (*(current++) == '\\' && *(current++)== 'u')
			{
				if (decodeUnicodeEscapeSequence( token, current, end, surrogatePair ))
				{
					unicode = 0x10000 + ((unicode & 0x3FF) << 10) + (surrogatePair & 0x3FF);
				} 
				else
					return false;
			} 
			else
				return addError( "expecting another \\u token to begin the second half of a unicode surrogate pair", token, current );
		}
		return true;
	}

	bool Reader::decodeUnicodeEscapeSequence( Token &token, Location &current, Location end, unsigned int &unicode )
	{
		if ( end - current < 4 )
			return addError( "Bad unicode escape sequence in string: four digits expected.", token, current );
		unicode = 0;
		for ( int index =0; index < 4; ++index )
		{
			Char c = *current++;
			unicode *= 16;
			if ( c >= '0'  &&  c <= '9' )
				unicode += c - '0';
			else if ( c >= 'a'  &&  c <= 'f' )
				unicode += c - 'a' + 10;
			else if ( c >= 'A'  &&  c <= 'F' )
				unicode += c - 'A' + 10;
			else
				return addError( "Bad unicode escape sequence in string: hexadecimal digit expected.", token, current );
		}
		return true;
	}


	bool Reader::addError( const std::string &message, Token &token, Location extra )
	{
		ErrorInfo info;
		info.token_ = token;
		info.message_ = message;
		info.extra_ = extra;
		errors_.push_back( info );
		return false;
	}


	bool Reader::recoverFromError( TokenType skipUntilToken )
	{
		int errorCount = int(errors_.size());
		Token skip;
		while ( true )
		{
			if ( !readToken(skip) )
				errors_.resize( errorCount ); // discard errors caused by recovery
			if ( skip.type_ == skipUntilToken  ||  skip.type_ == tokenEndOfStream )
				break;
		}
		errors_.resize( errorCount );
		return false;
	}


	bool Reader::addErrorAndRecover( const std::string &message, Token &token, TokenType skipUntilToken )
	{
		addError( message, token );
		return recoverFromError( skipUntilToken );
	}


	Value &Reader::currentValue()
	{
		return *(nodes_.top());
	}


	Reader::Char Reader::getNextChar()
	{
		if ( current_ == end_ )
			return 0;
		return *current_++;
	}

	void Reader::getLocationLineAndColumn( Location location, int &line, int &column ) const
	{
		Location current = begin_;
		Location lastLineStart = current;
		line = 0;
		while ( current < location  &&  current != end_ )
		{
			Char c = *current++;
			if ( c == '\r' )
			{
				if ( *current == '\n' )
					++current;
				lastLineStart = current;
				++line;
			}
			else if ( c == '\n' )
			{
				lastLineStart = current;
				++line;
			}
		}
		// column & line start at 1
		column = int(location - lastLineStart) + 1;
		++line;
	}


	std::string	Reader::getLocationLineAndColumn( Location location ) const
	{
		int line, column;
		getLocationLineAndColumn( location, line, column );
		char buffer[18+16+16+1];
		sprintf( buffer, "Line %d, Column %d", line, column );
		return buffer;
	}


	std::string Reader::getFormatedErrorMessages() const
	{
		std::string formattedMessage;
		for ( Errors::const_iterator itError = errors_.begin();
			itError != errors_.end();
			++itError )
		{
			const ErrorInfo &error = *itError;
			formattedMessage += "* " + getLocationLineAndColumn( error.token_.start_ ) + "\n";
			formattedMessage += "  " + error.message_ + "\n";
			if ( error.extra_ )
				formattedMessage += "See " + getLocationLineAndColumn( error.extra_ ) + " for detail.\n";
		}
		return formattedMessage;
	}


	std::istream& operator>>( std::istream &sin, Value &root )
	{
		Json::Reader reader;
		bool ok = reader.parse(sin, root, true);
		//JSON_ASSERT( ok );
		if (!ok) throw std::runtime_error(reader.getFormatedErrorMessages());
		return sin;
	}


} // namespace Json
